package com.by.my;


import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.session.defaults.DefaultSqlSession;

import java.lang.reflect.Field;
import java.util.*;

/**
 * mybatis
 * 拦截器插件 并不是所有的拦截类中方法都能被拦截。 可以被拦截的所有的签名，如下
 */
@Intercepts({
	/*@Signature(//所有的insert update delete执行时调用
		type = Executor.class,
		method = "update",
		args = {MappedStatement.class, Object.class}
	),
	@Signature(//所有的select执行时调用
			type = Executor.class,
			method = "query",
			args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
	)*/
    /*@Signature(//该方法只有查询的返回值类型为Cursor是被调用
        type = Executor.class,
        method = "queryCursor",
        args = {MappedStatement.class, Object.class, RowBounds.class}
    )*/
    /*@Signature(//该方法只有在通过SqlSession方法中调用 flushStatements 方法或者执行的接口方法中带有 @Flush 注解时才被调用
        type = Executor.class,
        method = "flushStatements",
        args = {}
    )*/
    /*@Signature(//该方法只有在通过SqlSession方法中调用 commit 方法时才被调用
        type = Executor.class,
        method = "commit",
        args = {boolean.class}
    )*/
	/*@Signature(//该方法只有在通过SqlSession方法中调用 rollback 方法时才被调用
		type = Executor.class,
		method = "rollback",
		args = {boolean.class}
	)*/
	/*@Signature(//该方法只有在通过SqlSession方法获取数据库连接时才被调用
		type = Executor.class,
		method = "getTransaction",
		args = {}
	)*/
	/*@Signature(//该方法只延迟加载获取新的Executor后才会被执行
		type = Executor.class,
		method = "close",
		args = {boolean.class}
	)*/
	/*@Signature(//该方法只在延迟加载执行查询方法前被执行
		type = Executor.class,
		method = "isClosed",
		args = {}
	)*/

	//================================================
	/*@Signature(//该方法只在执行存储过程处理出参的时候调用
		type = ParameterHandler.class,
		method = "getParameterObject",
		args = {}
	)*/
	/*@Signature(//该方法在所有数据库昂发设置sql参数时被调用
		type = ParameterHandler.class,
		method = "getParameterObject",
		args = {PreparedStatement.class}
	)*/

	//================================================
	/*@Signature(//该方法会在除了存储过程及返回值类型为Cursor<T>(org.apache.ibatis.cursor.Cursor<T>)以外的查询方法中调用
				//对拦截处理Mybatis的查询结果非常有用，并且这个借口被调用的位置在处理二级缓存之前，因为通过这种方式处理的结果可以
				//执行二级缓存
		type = ResultSetHandler.class,
		method = "handleResultSets",
		args = {Statement.class}
	)*/
	/*@Signature(//3.4.0版本新增的方法
			// 该方法只在返回值类型为Cursor<T>的查询方法中被调用
		type = ResultSetHandler.class,
		method = "handleCursorResultSets",
		args = {Statement.class}
	)*/
	/*@Signature(// 该方法只在使用存储过程处理出参时被调用
		type = ResultSetHandler.class,
		method = "handleOutputParameters",
		args = {CallableStatement.class}
	)*/

	//================================================
	/*@Signature(//该方法会在数据库执行前被调用，优先于当前借口中的其他方法而被执行
		type = StatementHandler.class,
		method = "prepare",
		args = {Connection.class, Integer.class}
	)*/
	/*@Signature(// 该方法在 prepare 方法之后执行，用于处理参数信息
			// 设置全局配置 defaultExecutorType="BATCH" 时，执行数据操作才会被调用该方法
		type = StatementHandler.class,
		method = "parameterize",
		args = {Statement.class}
	)*/
	/*@Signature(// 设置全局配置 defaultExecutorType="BATCH" 时，执行数据操作才会被调用该方法
		type = StatementHandler.class,
		method = "batch",
		args = {Statement.class}
	)*/
	/*@Signature(// 在执行 select 方法是调用
		type = StatementHandler.class,
		method = "query",
		args = {Statement.class, ResultHandler.class}
	)*/
	/*@Signature(//3.4.0新增的 只在返回值类型为Cusor<T>的查询中被调用
		type = StatementHandler.class,
		method = "queryCursor",
		args = {Statement.class}
	)*/
})
@SuppressWarnings("all")
public class QueryIntercepter2 implements Interceptor {
    /**
     * https://blog.csdn.net/e_anjing/article/details/79102693
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
	    /*//当前被拦截的对象
    	Object target = invocation.getTarget();
    	//当前被拦截的方法
	    Method method = invocation.getMethod();
	    //被拦截方法中的参数
	    Object[] args = invocation.getArgs();
	    //
	    Object result = invocation.proceed();*/

    	/*
    	if(invocation.getTarget() instanceof Executor && invocation.getArgs().length == 2) {
		    MappedStatement ms = (MappedStatement)invocation.getArgs()[0];
		    BaseEntity baseEntity = (BaseEntity) invocation.getArgs()[1];
		    baseEntity.setInsetDatetime(new Date());
		    baseEntity.setUpdateDatetime(new Date());
	    }
	    */

	    String sql = getSqlByInvocation(invocation);
	    System.out.println("sql 查询拦截器:"+sql);

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {

	    // 借助 Plugin 的 wrap(Object target,Interceptor interceptor); 包装我们的目标对象
	    // target: 目标对象, interceptor: 拦截器, this 表示使用当前拦截器
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    private String getSqlByInvocation(Invocation invocation) {
        final Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        String sql = boundSql.getSql();
        return formatSql(sql, parameterObject, boundSql.getParameterMappings());

    }

    private void processMap(Map<String, Object> map) {
	    Set<String> keySet = new HashSet<String>(map.keySet());
	    for(String key : keySet) {
	    	//将以大写开头的字符串转换为小写，如果包含下划线也会处理为驼峰标识
		    //此处指通过这两个简单的标识来判断是否进行转换
		    if(key.charAt(0) >= 'A' && key.charAt(0) <= 'Z' || key.indexOf("_") >= 0 ) {
				Object value = map.get(key);
				map.remove(key);
				map.put(underlineToCamelhump(key), value);
		    }
	    }
    }

    public static String underlineToCamelhump(String inputString) {
    	StringBuilder sb = new StringBuilder();


    	boolean nextUpperCase = false;
    	for(int i=0, length=inputString.length(); i<length; i++) {
    		char c = inputString.charAt(i);
    		if(c=='_') {
    			if(sb.length()>0) {
    				nextUpperCase=true;
			    }
		    }else {
    			if(nextUpperCase) {
    				sb.append(Character.toUpperCase(c));
    				nextUpperCase = false;
			    }else {
    				sb.append(Character.toLowerCase(c));
			    }
		    }

	    }

    	return sb.toString();
    }

	@SuppressWarnings("unchecked")
	private String formatSql(String sql, Object parameterObject, List<ParameterMapping> parameterMappingList) {
		// 输入sql字符串空判断
		if (sql == null || sql.length() == 0) {
			return "";
		}

		// 美化sql
		sql = beautifySql(sql);

		// 不传参数的场景，直接把Sql美化一下返回出去
		if (parameterObject == null || parameterMappingList == null || parameterMappingList.size() == 0) {
			return sql;
		}

		// 定义一个没有替换过占位符的sql，用于出异常时返回
		String sqlWithoutReplacePlaceholder = sql;

		try {
			if (parameterMappingList != null) {
				Class<?> parameterObjectClass = parameterObject.getClass();

				// 如果参数是StrictMap且Value类型为Collection，获取key="list"的属性，这里主要是为了处理<foreach>循环时传入List这种参数的占位符替换
				// 例如select * from xxx where id in <foreach collection="list">...</foreach>
				if (isStrictMap(parameterObjectClass)) {
					DefaultSqlSession.StrictMap<Collection<?>> strictMap = (DefaultSqlSession.StrictMap<Collection<?>>)parameterObject;

					if (isList(strictMap.get("list").getClass())) {
						sql = handleListParameter(sql, strictMap.get("list"));
					}
				} else if (isMap(parameterObjectClass)) {
					// 如果参数是Map则直接强转，通过map.get(key)方法获取真正的属性值
					// 这里主要是为了处理<insert>、<delete>、<update>、<select>时传入parameterType为map的场景
					Map<?, ?> paramMap = (Map<?, ?>) parameterObject;
					sql = handleMapParameter(sql, paramMap, parameterMappingList);
				} else {
					// 通用场景，比如传的是一个自定义的对象或者八种基本数据类型之一或者String
					sql = handleCommonParameter(sql, parameterMappingList, parameterObjectClass, parameterObject);
				}
			}
		} catch (Exception e) {
			// 占位符替换过程中出现异常，则返回没有替换过占位符但是格式美化过的sql，这样至少保证sql语句比BoundSql中的sql更好看
			return sqlWithoutReplacePlaceholder;
		}

		return sql;
	}

	/**
	 * 美化Sql
	 */
	private String beautifySql(String sql) {
		// sql = sql.replace("\n", "").replace("\t", "").replace("  ", " ").replace("( ", "(").replace(" )", ")").replace(" ,", ",");
		sql = sql.replaceAll("[\\s\n ]+"," ");
		return sql;
	}

	/**
	 * 处理参数为List的场景
	 */
	private String handleListParameter(String sql, Collection<?> col) {
		if (col != null && col.size() != 0) {
			for (Object obj : col) {
				String value = null;
				Class<?> objClass = obj.getClass();

				// 只处理基本数据类型、基本数据类型的包装类、String这三种
				// 如果是复合类型也是可以的，不过复杂点且这种场景较少，写代码的时候要判断一下要拿到的是复合类型中的哪个属性
				if (isPrimitiveOrPrimitiveWrapper(objClass)) {
					value = obj.toString();
				} else if (objClass.isAssignableFrom(String.class)) {
					value = "\"" + obj.toString() + "\"";
				}

				sql = sql.replaceFirst("\\?", value);
			}
		}

		return sql;
	}

	/**
	 * 处理参数为Map的场景
	 */
	private String handleMapParameter(String sql, Map<?, ?> paramMap, List<ParameterMapping> parameterMappingList) {
		for (ParameterMapping parameterMapping : parameterMappingList) {
			Object propertyName = parameterMapping.getProperty();
			Object propertyValue = paramMap.get(propertyName);
			if (propertyValue != null) {
				if (propertyValue.getClass().isAssignableFrom(String.class)) {
					propertyValue = "\"" + propertyValue + "\"";
				}

				sql = sql.replaceFirst("\\?", propertyValue.toString());
			}
		}

		return sql;
	}

	/**
	 * 处理通用的场景
	 */
	private String handleCommonParameter(String sql, List<ParameterMapping> parameterMappingList, Class<?> parameterObjectClass,
	                                     Object parameterObject) throws Exception {
		for (ParameterMapping parameterMapping : parameterMappingList) {
			String propertyValue = null;
			// 基本数据类型或者基本数据类型的包装类，直接toString即可获取其真正的参数值，其余直接取paramterMapping中的property属性即可
			if (isPrimitiveOrPrimitiveWrapper(parameterObjectClass)) {
				propertyValue = parameterObject.toString();
			} else {
				String propertyName = parameterMapping.getProperty();

				Field field = parameterObjectClass.getDeclaredField(propertyName);
				// 要获取Field中的属性值，这里必须将私有属性的accessible设置为true
				field.setAccessible(true);
				propertyValue = String.valueOf(field.get(parameterObject));
				if (parameterMapping.getJavaType().isAssignableFrom(String.class)) {
					propertyValue = "\"" + propertyValue + "\"";
				}
			}

			sql = sql.replaceFirst("\\?", propertyValue);
		}

		return sql;
	}

	/**
	 * 是否基本数据类型或者基本数据类型的包装类
	 */
	private boolean isPrimitiveOrPrimitiveWrapper(Class<?> parameterObjectClass) {
		return parameterObjectClass.isPrimitive() ||
				(parameterObjectClass.isAssignableFrom(Byte.class) || parameterObjectClass.isAssignableFrom(Short.class) ||
						parameterObjectClass.isAssignableFrom(Integer.class) || parameterObjectClass.isAssignableFrom(Long.class) ||
						parameterObjectClass.isAssignableFrom(Double.class) || parameterObjectClass.isAssignableFrom(Float.class) ||
						parameterObjectClass.isAssignableFrom(Character.class) || parameterObjectClass.isAssignableFrom(Boolean.class));
	}

	/**
	 * 是否DefaultSqlSession的内部类StrictMap
	 */
	private boolean isStrictMap(Class<?> parameterObjectClass) {
		return parameterObjectClass.isAssignableFrom(DefaultSqlSession.StrictMap.class);
	}

	/**
	 * 是否List的实现类
	 */
	private boolean isList(Class<?> clazz) {
		Class<?>[] interfaceClasses = clazz.getInterfaces();
		for (Class<?> interfaceClass : interfaceClasses) {
			if (interfaceClass.isAssignableFrom(List.class)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * 是否Map的实现类
	 */
	private boolean isMap(Class<?> parameterObjectClass) {
		Class<?>[] interfaceClasses = parameterObjectClass.getInterfaces();
		for (Class<?> interfaceClass : interfaceClasses) {
			if (interfaceClass.isAssignableFrom(Map.class)) {
				return true;
			}
		}

		return false;
	}

}
